/* $Header: det_states.c,v 2.2 01/10/02 10:26:05 przybyls Exp $ */

/************************************************************************
** FILE NAME:   det_states.c
**
** Author:      John DeGood
**
** Comment:     Detector state machines.
**
** Theory of Operation:
**
** The "xxxStateTable" states are numbered by index value:  the first
** state is "0", the next is "1", and so forth.  Each state is defined by 4
** parameters:
**
**      next state
**      time
**      transition function
**      body function
**
** The state "transition function" is executed once at entry to the state.
** The state "body function" is executed immediately following the state
** "transition function" and is then executed once per second while in the
** state.
**
** The state machine advances to the "next state" after the "time" in
** seconds elapses for time > 0.  If time == 0 the state is immediately
** exited after the transition and body functions are executed once.  If
** time < 0 the state will persist forever until an external event forces a
** transition.
**
** A state transition can occur at any time as the result of some external
** stimulus.  For example, turning the flame setpoint "on" initiates an
** immediate transition to the FLAME_WAIT state and turning the flame
** setpoint "off" initiates an immediate transition to the FLAME_OFF state.
**
*************************************************************************
**                Copyright Hewlett-Packard Co. 1992                   **
*************************************************************************
*/

/* global include files */
#include <typedefs.h>
#include <i_op.h>
#include <iomap.h>
#include <error.h>
#include <p_status.h>
#include <det_ui.h>
#include <zone_config.h>
#include <p_wkfile.h>
#include <wkfile.h>
#include <ramps.h>
#include <zone_status.h>
#include <status.h>
#include <flow.h>
#include <p_ui.h>
#include <zone_ui.h>
#include <detector.h>
#include <sig_types.h>
#include <sig_ui.h>

#include <exception.h>
#include <err_handler.h>
#include <stdlib.h>     /* for abs() */

/* range of duty cycles for non-EPC FID that reliably ignite, per Paul Welsh testing */
#define FID_LO_CYCLE    10                                      /* duty cycle (percent) */
#define FID_HI_CYCLE    70                                      /* duty cycle (percent) */

/* range of duty cycles for EPC FID that reliably ignite */
/* REVISIT - the following flow values are a wild guess -- how do they correspond to duty cycles??? */
#define FID_NOMINAL_IGNITION_FLOW       400                             /* typical air flow (ml/min) */
#define FID_LO_FLOW     FID_NOMINAL_IGNITION_FLOW*FID_LO_CYCLE/100      /* flow rate (ml/min) */
#define FID_HI_FLOW     FID_NOMINAL_IGNITION_FLOW*FID_HI_CYCLE/100      /* flow rate (ml/min) */

/* range of duty cycles for non-EPC FPD that reliably ignite, per John DeGood testing */
#define FPD_LO_CYCLE    10                                      /* duty cycle (percent) */
#define FPD_HI_CYCLE    70                                      /* duty cycle (percent) */

/* range of flows for EPC FPD that reliably ignite */
#define FPD_NOMINAL_IGNITION_FLOW       100                             /* typical H2 flow (ml/min) */
#define FPD_LO_FLOW     FPD_NOMINAL_IGNITION_FLOW*FPD_LO_CYCLE/100      /* flow rate (ml/min) */
#define FPD_HI_FLOW     FPD_NOMINAL_IGNITION_FLOW*FPD_HI_CYCLE/100      /* flow rate (ml/min) */

#define FPD_AIR_IGNITION_FLOW   2000            /* desired air flow (1/10th ml/min) */

/* to prevent condensation allow flame only if detector is >= this temperature */
#define FLAME_MIN_OPERATING_TEMP        150
#define FPD_MIN_OPERATING_TEMP          120
#define NPD_MIN_OPERATING_TEMP          150

/* give up (shutdown) after this many attempts to light a flame */
#define MAX_IGNITE_ATTEMPTS             2

/*
** hysteresis bounds for TCD filament temperature, per Bob Rhodes (19 Jul 94)
** NOTE:  if these limits are set too small the detector zone will
**        oscillate because of the changing heat from the filament!
** Note2:  the below 5 defs moved to detector.h 4/97 to support filament ON improvements ... SOK
#define TCD_FILAMENT_MIN_ON_TEMP        150                             /~ turn filament on if det is >= this temp ~/
#define TCD_FILAMENT_MIN_OFF_TEMP       (TCD_FILAMENT_MIN_ON_TEMP - 10) /~ turn filament off if det is <= this temp ~/
#define TCD_FILAMENT_TEMP_TRANSITION    200
#define TCD_FILAMENT_TEMP_LO_THRESHOLD  (TCD_FILAMENT_TEMP_TRANSITION - 5)
#define TCD_FILAMENT_TEMP_HI_THRESHOLD  (TCD_FILAMENT_TEMP_TRANSITION + 5)
*/

/* valve duty cycle frequency is 0.5 Hz, so it takes 2 seconds per cycle */
/* for example, a RAMPTIME of 16 seconds will result in 8 valve cycles */
#define RAMPTIME        16                                      /* air ramp time (seconds) */

/*
** ADC limits for TCD filament failure detection:  we use bridge voltage limits
** of 0.05 volts to 0.75 volts to indicate a good TCD filament, per Bob Rhodes.  (10 Nov 93)
**
** Lower limit approached at det_temp = 400 deg C and low Ar flow rate.
** Upper limit approached at det_temp = (TCD_FILAMENT_TEMP_TRANSITION + 1) and high H2 flow rate.
**
** Low values -> open filament, high values -> shorted filament.
** ADC full scale is 65535 counts @ 1V.
*/
#define TCD_FILAMENT_OPEN_RDG   3277    /* 0.05 volts */
#define TCD_FILAMENT_SHORT_RDG  49151   /* 0.75 volts */

/* number of consecutive readings required to initiate an action */
#define DEGLITCH_COUNT  6

/* detector state table entry */
typedef struct
{
        U_INT16 next;
        INT16 time;             /* seconds:  0 is immediate exit, <0 is never exit */
        void (* transition)(POSITION p);
        void (* body)(POSITION p);
} StateTableEntry;

/* detector state table */
typedef StateTableEntry StateTable[];

/* detector diagnostic readings */
typedef struct
{
        U_INT16 vout1;          /* V Out 1 from Mux ADC after activation */
        U_INT32 preOffset;      /* signal before activation */
        U_INT32 midOffset;      /* signal during activation */
        U_INT32 postOffset;     /* signal after activation */
} DiagReadings;

/* state of a detector state machine */
typedef struct
{
        U_INT8 state;           /* current state */
        POSITION p;             /* FRONT | BACK */
        U_INT8 attempts;        /* number of attempts, e.g. for flame ignition:  is NOT reset on state transitions! */
        U_INT8 counter;         /* counter to deglitch normal transitions */
        U_INT8 errors;          /* counter to deglitch error transitions */
        U_INT32 timeStart;      /* time current state began */
        U_INT32 timeLast;       /* last time body function called */
        INT32 timeDuration;     /* time to stay in current state in seconds:  0 is immediate exit, <0 is never exit */
        INT32 scratch;          /* general purpose instance variable */
        INT32 itch;             /* general purpose instance variable */
        DiagReadings diag;      /* diagnostic saved ADC readings */
        StateTable *table;      /* the state table for this kind of detector */
} StateMachine;

/* static function prototypes */
static void DoStateTransition(StateMachine *det, U_INT8 state);
static void NextState(StateMachine *det);
static void GotoState(StateMachine *det, U_INT8 state);
static void NullFunction(POSITION p);

static U_INT8 TCDRefGasOK( POSITION p);

static pDETECTOR_STATUS pDetPtr(POSITION p);
static INT32 DetOffset(POSITION p);
static U_INT16 DetVOut1(POSITION p);
static U_INT32 IgnitionFlow(POSITION p);
static U_INT8 IgnitionDutyCycle(POSITION p);

static void FlameTransition1(POSITION p);
static void FlameBody1(POSITION p);
static void FlameTransition2(POSITION p);
static void FlameTransition3(POSITION p);
static void FlameTransition4(POSITION p);
static void FlameBody4(POSITION p);
static void FlameTransition5(POSITION p);
static void FlameBody5(POSITION p);
static void FlameTransition6(POSITION p);
static void FlameBody6(POSITION p);
static void FlameTransition7(POSITION p);
static void FlameAbort(POSITION p);
static INT16 FlameOut(POSITION p);
static void FlameIgnitionFailure(POSITION p);

static void TCDBody1(POSITION p);
static void TCDTransition2(POSITION p);
static void TCDBody2(POSITION p);
static void TCDTransition3(POSITION p);
static void TCDBody3(POSITION p);
static void TCDTransition4(POSITION p);
static void TCDAbort(POSITION p);
static INT16 TCDFilamentFailure(POSITION p);

static void NPDTransition1(POSITION p);
static void NPDBody1(POSITION p);
static void NPDTransition2(POSITION p);
static void NPDBody2(POSITION p);
static void NPDTransition3(POSITION p);
static void NPDBody3(POSITION p);
static void NPDTransition4(POSITION p);
static void NPDBody4(POSITION p);
static void NPDTransition6(POSITION p);
static void NPDAdjustBody(POSITION p);
static void NPDTransition7(POSITION p);
static void NPDTransition8(POSITION p);
static void NPDTransition9(POSITION p);
static void NPDTransition10 (POSITION p);
static void NPDBody10(POSITION p);
static void NPDTransitionShutdown(POSITION p);
static void NPDAbort(POSITION p);

static void ECDBody1(POSITION p);
static void ECDTransition2(POSITION p);
static void ECDTransition3(POSITION p);
static void ECDTransition4(POSITION p);
static void ECDTransition5(POSITION p);

static void ConvrgNPDVoltage(POSITION p );

static void ChkOEMPulses(POSITION p);
static void OEMDetService(POSITION p);
static void OEMDetRdyChk(POSITION p);

/* Null state table for detectors that don't need one */
static const StateTable NullStateTable =
{
        /* next, time, transition, body */
        {0,  0,         NullFunction,           NullFunction}   /* 0 - idle */
};

/* front and back detector state machine states */
static StateMachine sm[MAX_DETECTORS];

static const EXCEPTION Oem_Rdy_Xcptn[] = { FRNT_OEM_DET_NRDY, FRNT_OEM_DET_NRDY1, FRNT_OEM_DET_NRDY2,
                                           FRNT_OEM_DET_NRDY3, FRNT_OEM_DET_NRDY4, FRNT_OEM_DET_NRDY5,
                                           FRNT_OEM_DET_NRDY6, FRNT_OEM_DET_NRDY7, FRNT_OEM_DET_NRDY8,

                                           BACK_OEM_DET_NRDY, BACK_OEM_DET_NRDY1, BACK_OEM_DET_NRDY2,
                                           BACK_OEM_DET_NRDY3, BACK_OEM_DET_NRDY4, BACK_OEM_DET_NRDY5,
                                           BACK_OEM_DET_NRDY6, BACK_OEM_DET_NRDY7, BACK_OEM_DET_NRDY8 };

#define NUM_OF_NTRYS sizeof(Oem_Rdy_Xcptn )/sizeof(EXCEPTION)


/***********************************************************************
                        general state machine functions
***********************************************************************/

/* Execute actions associated with a det state machine state transition */

static void DoStateTransition(StateMachine *det, U_INT8 state)
{
        /* reset counters on transition to a new state */
        if (det->state != state)
        {
                det->errors = 0;
                det->counter = 0;
        }

        /* transition to the new state */
        det->state = state;

        INTERRUPTS_OFF
        det->timeStart = Uptime_Clock;
        det->timeLast = Uptime_Clock;
        det->timeDuration = (*det->table)[det->state].time;
        INTERRUPTS_ON

        /* execute state transition function */
        ((*det->table)[det->state].transition)(det->p);

        /* execute state body function */
        ((*det->table)[det->state].body)(det->p);

        /* if the state time == 0 and this is not the idle state, then advance immediately to the next state */
        if (det->timeDuration == 0 && det->state != 0)
        {
                /* use recursion, but hopefully shallow! */
                NextState(det);
        }
}

/* Advance to the next state in the detector state machine */

static void NextState(StateMachine *det)
{
        DoStateTransition(det, (*det->table)[det->state].next);
}

/* Jump to a new state in the detector state machine */

static void GotoState(StateMachine *det, U_INT8 state)
{
        DoStateTransition(det, state);
}

/* Detector subtask invoked by StatusMonitor() */

void DetectorSubtask(void)
{
        int i;

        for (i = 0; i < MAX_DETECTORS; i++)
        {
                /* run the front and back detector state machines if not in the idle state */
                if (sm[i].state > 0)
                {
                        /* has time for this state expired? */
                        /* a negative timeDuration means we never leave this state */
                        if ( (sm[i].timeDuration >= 0) && ((INT32)(Uptime_Clock - sm[i].timeStart) >= sm[i].timeDuration) )
                        {
                                /* advance to next state */
                                NextState(&sm[i]);
                        }
                        /* has a clock tick occurred since the body function was last called? */
                        else if (sm[i].timeLast != Uptime_Clock)
                        {
                                /* update timeLast */
                                sm[i].timeLast = Uptime_Clock;

                                /* execute body function for this state */
                                ((*sm[i].table)[sm[i].state].body)((POSITION)i);
                        }
                }
        }
}

/***********************************************************************
                        miscellaneous functions
***********************************************************************/

/* do-nothing function */

static void NullFunction(POSITION p)
{
        /* dummy use of param to squelch compiler warning */
        (void) p;       /* cc68332 emits no code for this */
}

/* convert POSITION to pneumatics pDETECTOR_STATUS */

static pDETECTOR_STATUS pDetPtr(POSITION p)
{
        return ((p == FRONT) ? pFRONT_DETECTOR : pBACK_DETECTOR);
}

/* DetOffset - return filtered detector offset scaled to user units */

static INT32 DetOffset(POSITION p)
{
        INT32 offset;

        /* sample filtered det signal used by Col Comp */
        offset = (p == FRONT) ? Deta_Filt.filt_out.output : Detb_Filt.filt_out.output;

        /* scale value to return user units */
        return (offset / SIG_DISP_SCALE_FACTOR);
}

/* return current value of detector diagnostic VOut1 */

static U_INT16 DetVOut1(POSITION p)
{
        return ((p == FRONT) ?  Det_Diaga_Rdg : Det_Diagc_Rdg);
}

/************************************************************************
                        Flame (FID/FPD) States
************************************************************************/

static INT32  GetFlameMinOperatingTemp( POSITION p )
{
   DET_TYPE type;

   type = GetDetectorType( p );

   if ( type == FID ) return FLAME_MIN_OPERATING_TEMP;

   return FPD_MIN_OPERATING_TEMP;
}

/* FID and FPD flame on/off state table */
static const StateTable FlameStateTable =
{
        /* next, time, transition, body */

        /* off state */
#define FLAME_OFF 0
        {0,  0,                 NullFunction,           NullFunction},  /* 0 - flame off */

        /* waiting for detector temp states */
#define FLAME_WAIT 1
        {1, -1,                 FlameTransition1,       FlameBody1},    /* 1 - wait for detector
                                                                        temp > min flame temp */

        /* flame ignition states */

#define FLAME_START_IGNITE 2
        {3,  3,                 FlameTransition2,       NullFunction},  /* 2 - H2 off */
        {4,  2,                 FlameTransition3,       NullFunction},  /* 3 - igniter on */
        {5,  RAMPTIME,          FlameTransition4,       FlameBody4},    /* 4 - ramp air flow */

        /* determine lit status after delay for thermal emission from igniter to subside      */
        /* we test status here to make sure the flame really is lit before declaring it in    */
        /* the display                                                                        */
        {6,  DEGLITCH_COUNT+4,  FlameTransition5,       FlameBody5},    /* 5 - igniter off,
                                                                       determine if flame lit */
#define FLAME_END_IGNITE 5

        /* flame on state */
#define FLAME_ON 6
        {6, -1,                 FlameTransition6,       FlameBody6},    /* 6 - monitor detctor
                                                                      temp & flame lit status */

        /* flame shutdown state */
#define FLAME_SHUTDOWN 7
        {7, -1,                 FlameTransition7,       NullFunction},  /* 7 - shutdown due to pneumatics or ignition failure */
};

/* FlameOut - returns TRUE if flame is not lit */

static INT16 FlameOut(POSITION p)
{
        INT16 flameout= FALSE;
        INT32 difference;

        /* (det_lit_offset == 0) means do not test flame lit status */
        if (Active_Wkfile.det_lit_offset[(int)p] == 0)
        {
                return FALSE;
        }

        /* is flame lit?  compare current offset to pre-ignition detector offset */
        difference = DetOffset(p) - sm[(int)p].diag.preOffset;

        /* flame failed if difference is less than the user-specified offset */
        if (difference < Active_Wkfile.det_lit_offset[(int)p])
        {
                /* deglitch ADC readings:  return error only after several consecutive bad readings */
                if (++sm[(int)p].errors >= DEGLITCH_COUNT)
                {
                        flameout = TRUE;
                }
        }
        else  /* flame lit */
        {
                /* clear error count */
                sm[(int)p].errors = 0;
        }

        return flameout;
}

/* Flame state transition: waiting for detector to warmup */

static void FlameTransition1(POSITION p)
{
        /* temporarily disable H2 & air flow */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasOFF(p, FID_FUEL_GAS);
        DetGasOFF(p, FID_AIR_GAS);
}

/* Flame state body: waiting for detector to warmup */

static void FlameBody1(POSITION p)
{
   INT32    tempActual;

   tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

   /* wait for detector temperature warmup if tempActual is < minimum */
   /* don't deglitch this transition:  we don't want the display to indicate "waiting"
   if the detector is already warm, and a "false" start is harmless */
   if (tempActual>= GetFlameMinOperatingTemp(p))
   {
      GotoState(&sm[(int)p], FLAME_START_IGNITE);
   }
}

/* Flame state transition: extinguish flame */

static void FlameTransition2(POSITION p)
{
        /* temporarily disable H2 flow */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasOFF(p, FID_FUEL_GAS);

        /* temporarily disable make-up setpoint */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasOFF(p, FID_MUG_GAS);
}

/* Flame state transition: energize igniter */

static void FlameTransition3(POSITION p)
{
   /* record pre-ignition detector offset */
   sm[(int)p].diag.preOffset = DetOffset(p);

   /* turn on igniter */
   SetDetInput1(p, ON);

   /* turn on non-ramped ignition gas flow */
   if (GetDetectorType(p) == FID)
   {
      DetGasON(p, FID_FUEL_GAS);
   }
   else
   {
      PutDetAirSetptImmed(FPD_AIR_IGNITION_FLOW,((p==FRONT)?pFRONT_DETECTOR:pBACK_DETECTOR));
   }
}

/* Flame state transition: ignition ramp */

static void FlameTransition4(POSITION p)
{
        /* record igniter circuit voltage */
        sm[(int)p].diag.vout1 = DetVOut1(p);

        /* record mid-ignition detector offset */
        sm[(int)p].diag.midOffset = DetOffset(p);

        /* begin sweep of air setpoint */
        StartDetIgnitionSeq(p);

        /* set initial sweep setpt BEFORE DetGasOn() to avoid turnon glitch */
        if (GetDetectorType(p) == FID)
        {
                IsDetectorEpc(p)
                        ? SetDetGasIgnitionFlow(p, FID_AIR_GAS, IgnitionFlow(p))
                        : SetDetDutyCycle(p, FID_AIR_GAS, IgnitionDutyCycle(p));

                DetGasON(p, FID_AIR_GAS);
        }
        else
        {
                IsDetectorEpc(p)
                        ? SetDetGasIgnitionFlow(p, FPD_FUEL_GAS, IgnitionFlow(p))
                        : SetDetDutyCycle(p, FPD_FUEL_GAS, IgnitionDutyCycle(p));

                DetGasON(p, FPD_FUEL_GAS);
        }
}

/* Flame state body: ignition ramp */

static void FlameBody4(POSITION p)
{
        /* ramp gas flow to induce ignition */
        if (GetDetectorType(p) == FID)
        {
                IsDetectorEpc(p)
                        ? SetDetGasIgnitionFlow(p, FID_AIR_GAS, IgnitionFlow(p))
                        : SetDetDutyCycle(p, FID_AIR_GAS, IgnitionDutyCycle(p));
        }
        else
        {
                IsDetectorEpc(p)
                        ? SetDetGasIgnitionFlow(p, FPD_FUEL_GAS, IgnitionFlow(p))
                        : SetDetDutyCycle(p, FPD_FUEL_GAS, IgnitionDutyCycle(p));
        }
}

/* Flame state transition: turn off igniter, determine if flame lit */

static void FlameTransition5(POSITION p)
{
        /* end sweep of air setpoint */
        EndDetIgnitionSeq(p);

        /* restore make-up setpoint */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasON(p, FID_MUG_GAS);

        /* turn off igniter */
        SetDetInput1(p, OFF);
}

/* Flame state body : turn off igniter, determine if flame lit */

static void FlameBody5(POSITION p)
{
        /* monitor flame lit status */
        if (FlameOut(p))
        {
                FlameIgnitionFailure(p);
        }

        /* record post-ignition detector offset */
        /* do this here instead of in transition to lit state so we have a record of the value if ignition failed */
        sm[(int)p].diag.postOffset = DetOffset(p);
}

/* Flame state body: monitor detctor temp & flame lit status */

static void FlameTransition6(POSITION p)
{
        /* ignition was successful, so reset ignition attempt counter */
        sm[(int)p].attempts = 0;
}

/* Flame state body: monitor detctor temp & flame lit status */

static void FlameBody6(POSITION p)
{
   INT32 tempActual;

   /* monitor flame lit status */
   if (FlameOut(p))
   {
      FlameIgnitionFailure(p);
   }

   /* monitor detector temperature:  go to "waiting" state if tempActual is < (minimum - hysteresis) */
   tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

   if (tempActual < (GetFlameMinOperatingTemp(p) - 1))
   {
      /* use counter to deglitch transitions */
      if (++sm[(int)p].counter >= DEGLITCH_COUNT)
      {
         GotoState(&sm[(int)p], FLAME_WAIT);
      }
   }
   else
   {
      sm[(int)p].counter = 0;
   }
}

/* Flame state transition: safety shutdown due to flame ignition failure or pneumatics failure */

static void FlameTransition7 (POSITION p)
{
        /* pneumatics system turns all detector gases off after a shutdown, but
           we need to do it here in case this shutdown was from an ignition failure */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasOFF(p, FID_FUEL_GAS);
        DetGasOFF(p, FID_AIR_GAS);

        /* restore air setpoint */
        EndDetIgnitionSeq(p);

        /* turn off igniter to protect it from damage */
        SetDetInput1(p, OFF);
}


/* Abort a flame ignition/extinguish already in progress  */

static void FlameAbort(POSITION p)
{
        /* clear non-fatal errors */
        ClearNewException((p == FRONT) ? FRNT_FLAME_FAILURE : BACK_FLAME_FAILURE);

        /* stop state machine */
        GotoState(&sm[(int)p], 0);

        /* turn off igniter */
        SetDetInput1(p, OFF);

        /* restore H2 setpoint */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasON(p, FID_FUEL_GAS);

        /* restore make-up setpoint */
        /* NOTE: identical manifold positions are used for both FID and FPD */
        DetGasON(p, FID_MUG_GAS);

        /* restore air setpoint */
        EndDetIgnitionSeq(p);
}

/* Initiate a flame ignition sequence */

void FlameIgnite(POSITION p)
{
        /* abort an ignition/extinguish that might already be in progress */
        FlameAbort(p);

        /* reset ignition attempt counter to mark this as the first attempt */
        sm[(int)p].attempts = 1;

        /* start ignition state machine */
        GotoState(&sm[(int)p], FLAME_WAIT);

        /* set flame setpoint to "ON" */
        if (p == FRONT)
        {
                Active_Wkfile.det_flags.flag.deta_flame = (int) ON;
        }
        else
        {
                Active_Wkfile.det_flags.flag.detb_flame = (int) ON;
        }
}

/* Initiate an flame extinguish sequence */

void FlameExtinguish(POSITION p)
{
        /* abort an ignition/extinguish that might already be in progress */
        FlameAbort(p);

        /* start extinguish state machine */
        GotoState(&sm[(int)p], FLAME_OFF);

        /* set flame setpoint to "OFF" */
        if (p == FRONT)
        {
                Active_Wkfile.det_flags.flag.deta_flame = (int) OFF;
        }
        else
        {
                Active_Wkfile.det_flags.flag.detb_flame = (int) OFF;
        }
}

/* Initiate a flame shutdown sequence */

void FlameShutdown(POSITION p)
{
        GotoState(&sm[(int)p], FLAME_SHUTDOWN);
}

/* Handle a failed flame ignition */

static void FlameIgnitionFailure(POSITION p)
{
        /*
        ** Try to re-ignite the flame if we haven't already failed an excessive number of times.
        **
        ** CAUTION:  don't attempt to reignite if either H2 or air is OFF or set to zero flow because
        ** user might be measuring flows with an external device, e.g. a soap film bubble flow meter.
        **
        ** NOTE: identical manifold positions are used for both FID and FPD.
        */
        if ((++(sm[(int)p].attempts) <= MAX_IGNITE_ATTEMPTS)  &&
            (GetDetGasState (pDetPtr(p), FID_AIR_GAS)  == ON) &&
            (GetDetGasState (pDetPtr(p), FID_FUEL_GAS) == ON) &&
            !(IsDetectorEpc(p) && ((GetDetectorGasFlowSetpt(pDetPtr(p), FID_FUEL_GAS) == 0) ||
                                   (GetDetectorGasFlowSetpt(pDetPtr(p), FID_AIR_GAS)  == 0))))
        {
                /* restart the ignition sequence */
                GotoState(&sm[(int)p], FLAME_WAIT);
        }
        else
        {
                /* set non-fatal error */
                SetNewException((p == FRONT) ? FRNT_FLAME_FAILURE : BACK_FLAME_FAILURE, 0, 0);

                /* do shutdown because too many ignition failures have occurred */
                GotoState(&sm[(int)p], FLAME_SHUTDOWN);
        }
}

/* calculate EPC flow rate for ignition air ramp */

static U_INT32 IgnitionFlow(POSITION p)
{
        int elapsed;

        /* calculate how many seconds into the ramp we are */
        /* the range of "elapsed" time is 0 through (RAMPTIME - 1) */
        elapsed = Uptime_Clock - sm[(int)p].timeStart;

        /* clamp elapsed time in case Uptime_Clock just ticked */
        if (elapsed >= RAMPTIME)
        {
                elapsed = RAMPTIME - 1;
        }

        /* calculate flow (ml/min) */
        if (GetDetectorType(p) == FID)
        {
                return ( (elapsed * (FID_HI_FLOW - FID_LO_FLOW) ) / (RAMPTIME - 1) ) + FID_LO_FLOW;
        }
        else
        {
                return ( (elapsed * (FPD_HI_FLOW - FPD_LO_FLOW) ) / (RAMPTIME - 1) ) + FPD_LO_FLOW;
        }
}

/* calculate dirtball duty cycle for ignition air ramp */

static U_INT8 IgnitionDutyCycle(POSITION p)
{
        int elapsed;

        /* calculate how many seconds into the ramp we are */
        /* the range of "elapsed" time is 0 through (RAMPTIME - 1) */
        elapsed = Uptime_Clock - sm[(int)p].timeStart;

        /* clamp elapsed time in case Uptime_Clock just ticked */
        if (elapsed >= RAMPTIME)
        {
                elapsed = RAMPTIME - 1;
        }

        /* calculate duty cycle (percent) */
        if (GetDetectorType(p) == FID)
        {
                return ( (elapsed * (FID_HI_CYCLE - FID_LO_CYCLE) ) / (RAMPTIME - 1) ) + FID_LO_CYCLE;
        }
        else
        {
                return ( (elapsed * (FPD_HI_CYCLE - FPD_LO_CYCLE) ) / (RAMPTIME - 1) ) + FPD_LO_CYCLE;
        }
}

/* return ON_OFF status of FID or FPD flame */

ON_OFF FlameStatus(POSITION p)
{
        /* is detector on? */
        if (sm[(int)p].state == FLAME_ON)
        {
                return ON;
        }

        /* is detector off? */
        else if (sm[(int)p].state == FLAME_OFF)
        {
                return OFF;
        }

        /* are we waiting for the detector to get warm? */
        else if (sm[(int)p].state == FLAME_WAIT)
        {
                return WAITING;
        }

        /* are we igniting the flame? */
        else if ( (sm[(int)p].state >= FLAME_START_IGNITE) && (sm[(int)p].state <= FLAME_END_IGNITE) )
        {
                return TRANSITION;
        }

        /* shutdown is the only remaining mode */
        else
        {
                return SHUTDOWN;
        }
}

/************************************************************************
                                TCD States
************************************************************************/

/* TCD filament temp setpoint state table */
static const StateTable TCDStateTable =
{
        /* next, time, transition, body */

#define TCD_OFF 0
        {0,  0,         NullFunction,           NullFunction},  /* 0 - filament off */

#define TCD_WAIT 1
        {1, -1,         NullFunction,           TCDBody1},      /* 1 - wait for detector warmup */

#define TCD_LO_TEMP 2
        {2, -1,         TCDTransition2,         TCDBody2},      /* 2 - low filament temp */

#define TCD_HI_TEMP 3
        {3, -1,         TCDTransition3,         TCDBody3},      /* 3 - high filament temp */

#define TCD_SHUTDOWN 4
        {4, -1,         TCDTransition4,         NullFunction}   /* 4 - filament shutdown */
};

static INT16 TCDFilamentFailure(POSITION p)
{
        U_INT16 v;
        int failure = FALSE;

        v = DetVOut1(p);

        /* is TCD filament open? */
        if (v < TCD_FILAMENT_OPEN_RDG)
        {
                /* deglitch ADC readings:  return error only after consecutive bad readings */
                if (++sm[(int)p].errors >= DEGLITCH_COUNT)
                {
                        /* remember filament bridge voltage at time of failure */
                        sm[(int)p].diag.vout1 = v;

                        /* set non-fatal error */
                        SetNewException((p == FRONT) ? FRNT_TCD_OPEN_FILAMENT : BACK_TCD_OPEN_FILAMENT, 0, 0);

                        failure = TRUE;
                }
        }
        /* is TCD filament shorted? */
        else if (v > TCD_FILAMENT_SHORT_RDG)
        {
                /* deglitch ADC readings:  return error only after consecutive bad readings */
                if (++sm[(int)p].errors >= DEGLITCH_COUNT)
                {
                        /* remember filament bridge voltage at time of failure */
                        sm[(int)p].diag.vout1 = v;

                        /* set non-fatal error */
                        SetNewException((p == FRONT) ? FRNT_TCD_SHORT_FILAMENT : BACK_TCD_SHORT_FILAMENT, 0, 0);

                        failure = TRUE;
                }
        }
        else  /* filament OK */
        {
                /* clear error count */
                sm[(int)p].errors = 0;
        }

        return failure;
}

/* TCD state body: wait for detector warmup */
static void TCDBody1(POSITION p)
{
        INT32 tempSetpt, tempActual;

        tempSetpt = GetTempSetpt((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);
        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /*
        ** We check det temp BEFORE turning the detector on so the filament temp setpt is correct at turnon.
        ** Otherwise the bridge voltage would look like a filament failure if the actual det temp was hotter
        ** than the filament temp setpoint.
    ** We also require epc ref gas flow to keep amb. air from the filament & rely on 5.0 ml/min min setpt
    **                 OR non-epc ref gas flow to be on
        */

        if ( ( tempActual >= TCD_FILAMENT_MIN_ON_TEMP ) && TCDRefGasOK(p))
        {
                if ((tempActual > TCD_FILAMENT_TEMP_HI_THRESHOLD) ||
                   ((tempSetpt > TCD_FILAMENT_TEMP_TRANSITION) && (tempActual > TCD_FILAMENT_TEMP_LO_THRESHOLD)))
                {
                        /* don't deglitch this transition:  we don't want the display to indicate "waiting"
                           if the detector is already warm, and a "false" start is harmless */
                        GotoState(&sm[(int)p], TCD_HI_TEMP);
                }
                else
                {
                        /* don't deglitch this transition:  we don't want the display to indicate "waiting"
                           if the detector is already warm, and a "false" start is harmless */
                        GotoState(&sm[(int)p], TCD_LO_TEMP);
                }
        }
        else
        {
                /* turn filament WORKING setpoint OFF to prevent filament "sagging" failure in cool detector */
                SetDetOnOff(p, OFF);
        }
}

/* TCD state transition: set filament to low temp */
static void TCDTransition2(POSITION p)
{
        /* set filament to low temp */
        SetDetInput3(p, OFF);

        /* turn filament WORKING setpoint ON */
        SetDetOnOff(p, ON);
}

/* TCD state body: monitor filament low temp */
static void TCDBody2(POSITION p)
{
        INT32 tempSetpt, tempActual;

        tempSetpt = GetTempSetpt((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);
        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* turn off filament if ref_flowActual <= .8*minimum  */
        if (!TCDRefGasOK(p))
        {
            SetDetOnOff(p, OFF);
            GotoState(&sm[(int)p], TCD_WAIT);
        }

        /* turn off filament if tempActual <= (minimum - hysteresis) */
        else if (tempActual <= (TCD_FILAMENT_MIN_OFF_TEMP))
        {
                /* use counter to deglitch transitions */
                if (++sm[(int)p].counter >= DEGLITCH_COUNT)
                {
                        GotoState(&sm[(int)p], TCD_WAIT);
                }
        }
        /* currently at low filament temp; switch to high filament temperature if appropriate */
        else if ((tempActual > TCD_FILAMENT_TEMP_HI_THRESHOLD) ||
            ((tempSetpt > TCD_FILAMENT_TEMP_TRANSITION) && (tempActual > TCD_FILAMENT_TEMP_LO_THRESHOLD)))
        {
                /* use counter to deglitch transitions */
                if (++sm[(int)p].counter >= DEGLITCH_COUNT)
                {
                        GotoState(&sm[(int)p], TCD_HI_TEMP);
                }
        }
        /* check for open or shorted TCD filament */
        else if (TCDFilamentFailure(p))
        {
                /* transition to shutdown state */
                GotoState(&sm[(int)p], TCD_SHUTDOWN);
        }
        else
        {
                /* all is well */
                sm[(int)p].counter = 0;
        }
}

/* TCD state transition: set filament to high temp */
static void TCDTransition3(POSITION p)
{
        /* set filament to high temp */
        SetDetInput3(p, ON);

        /* turn filament WORKING setpoint ON */
        SetDetOnOff(p, ON);
}

/* TCD state body: monitor filament high temp */
static void TCDBody3(POSITION p)
{
        INT32 tempSetpt, tempActual;

        tempSetpt = GetTempSetpt((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);
        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* turn off filament if ref_flowActual <= .8*minimum  */
        if (!TCDRefGasOK(p))
        {
            SetDetOnOff(p, OFF);
            GotoState(&sm[(int)p], TCD_WAIT);
        }

        /* currently at high filament temp; switch to low filament temperature if appropriate */
        else if ((tempActual <= TCD_FILAMENT_TEMP_LO_THRESHOLD) ||
            ((tempSetpt <= TCD_FILAMENT_TEMP_TRANSITION) && (tempActual <= TCD_FILAMENT_TEMP_HI_THRESHOLD)))
        {
                /* use counter to deglitch transitions */
                if (++sm[(int)p].counter >= DEGLITCH_COUNT)
                {
                        GotoState(&sm[(int)p], TCD_LO_TEMP);
                }
        }
        /* check for open or shorted TCD filament */
        else if (TCDFilamentFailure(p))
        {
                /* transition to shutdown state */
                GotoState(&sm[(int)p], TCD_SHUTDOWN);
        }
        else
        {
                /* all is well */
                sm[(int)p].counter = 0;
        }
}

/* TCD state transition: safety shutdown after filament or pneumatics failure */
void TCDTransition4(POSITION p)
{
        /* pneumatics system has already turned all detector gases off if pneumatics shutdown,
           but we turn off the ref gas in case we got here because of a filament failure */
        DetGasOFF(p, TCD_REF_GAS);

        /* turn filament and reference gas WORKING setpoints off to protect TCD electronics from
           overheating in case of a short and protect TCD filament in case of a pneumatics failure */
        SetDetOnOff(p, OFF);
}

/* abort TCD operation already in progress */
static void TCDAbort(POSITION p)
{
        /* clear non-fatal errors */
        ClearNewException((p == FRONT) ? FRNT_TCD_OPEN_FILAMENT : BACK_TCD_OPEN_FILAMENT);
        ClearNewException((p == FRONT) ? FRNT_TCD_SHORT_FILAMENT : BACK_TCD_SHORT_FILAMENT);

        /* stop state machine */
        GotoState(&sm[(int)p], TCD_OFF);
}

/* TCDOn - turn on TCD filament */
void TCDOn(POSITION p)
{
        /* abort old TCD state */
        TCDAbort(p);

        /* set Active_Wkfile on/off setpoint to "ON" -- only the TCD state machine sets the working value */
        INTERRUPTS_OFF
        Active_Wkfile.detectors_on_off |= ((p == FRONT) ? DETA_BD_ON : DETB_BD_ON);
        INTERRUPTS_ON

        /* start state machine */
        GotoState(&sm[(int)p], TCD_WAIT);
}

/* TCDOff - turn off TCD filament */
void TCDOff(POSITION p)
{
        /* abort old TCD state */
        TCDAbort(p);

        /* set both Active_Wkfile and working value of on/off setpoint to "OFF" */
        (void)SetDetSetptOnOff(p, OFF);
}

/* Initiate a TCD shutdown sequence */

void TCDShutdown(POSITION p)
{
        GotoState(&sm[(int)p], TCD_SHUTDOWN);
}


/* return ON_OFF status of TCD */
ON_OFF TCDStatus(POSITION p)
{
        /* is the filament off? */
        if (sm[(int)p].state == TCD_OFF)
        {
                return OFF;
        }

        /* is the detector too cold? */
        else if (sm[(int)p].state == TCD_WAIT)
        {
                return WAITING;
        }

        /* was there a filament failure? */
        else if (sm[(int)p].state == TCD_SHUTDOWN)
        {
                return SHUTDOWN;
        }

        /* only choices left are the 2 filament on states */
        else
        {
                return ON;
        }
}

/************************************************************************
                                NPD States
************************************************************************/

/* NPD state table */
static const StateTable NPDStateTable =
{
        /* {next, time, transition, body}               state - description */
#define NPD_OFF 0
        {0,  0,         NullFunction,   NullFunction},  /* 0 - idle */
#define NPD_ON_WAIT_TEMP 1
        {1, -1,         NPDTransition1, NPDBody1},      /* 1 - wait for detector temp > 150 */
#define NPD_ON 2
        {2, -1,         NPDTransition2, NPDBody2},      /* 2 - normal operating state */
#define NPD_SEEK_WAIT_TEMP 3
        {3, -1,         NPDTransition3, NPDBody3},      /* 3 - wait for detector temp > 150 */
#define NPD_SEEK_WAIT_READY 4
        {4, -1,         NPDTransition4, NPDBody4},      /* 4 - wait for temperature readiness */
#define NPD_START_SEEK 5
        {6,  8,         NullFunction,   NullFunction},  /* 5 - sleep(8) */
#define NPD_START_COARSE 6
        {6,  8,         NPDTransition6, NPDAdjustBody}, /* 6 - adjust(coarse), sleep(8), repeat */
#define NPD_START_A 7
        {7,  999,       NPDTransition7, NPDAdjustBody}, /* 7 - adjust(a), sleep(a), repeat */
#define NPD_START_B 8
        {8,  999,       NPDTransition8, NPDAdjustBody}, /* 8 - adjust(b), sleep(b), repeat */
#define NPD_START_C 9
        {9,  999,       NPDTransition9, NPDAdjustBody}, /* 9 - adjust(c), sleep(c), repeat */
#define NPD_START_VALIDATE 10
        {2,  999,       NPDTransition10, NPDBody10},    /* 10 - validate bead setpt */
#define NPD_SHUTDOWN 11
        {11, -1,        NPDTransitionShutdown, NullFunction},   /* 11 - shutdown after pneumatic failure */
#define NPD_ADJUST_FAIL 12
        {12, -1,        NPDTransitionShutdown, NullFunction}    /* 12 - shutdown after adjust failure */
};

/*
** FineErrorOK - return TRUE if error is < max error allowed for this state
**                  returns error via pointer parameter
*/
static INT16 FineErrorOK(POSITION p, INT16 index, INT32 *error)
{
        /* offset error = target - actual */
        *error = (GetNPDTargetOffset(p) * 10) - DetOffset(p);

        return (abs(*error) <= NPDk(p, index));
}

/*
** NPDTooCold - monitor detector temperature:  return TRUE if tempActual is < (minimum - hysteresis)
*/
static INT16 NPDTooCold(POSITION p)
{
        INT32 tempActual;

        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* is tempActual < (minimum - hysteresis)? */
        if (tempActual < (NPD_MIN_OPERATING_TEMP - 1))
        {
                /* use counter to deglitch transitions */
                if (++sm[(int)p].counter >= DEGLITCH_COUNT)
                {
                        return TRUE;
                }
        }
        else
        {
                sm[(int)p].counter = 0;
        }

        return FALSE;
}

static void ConvrgNPDVoltage(POSITION p )
{
        if ( Active_Wkfile.npd_bead_voltage[(int)p] > DetSerial[(int)p] + NPD_MAX_1SEC_CHG )
        {
           DetSerial[(int)p] += NPD_MAX_1SEC_CHG ;
        }
        else
        {
           if ( Active_Wkfile.npd_bead_voltage[(int)p] != DetSerial[(int)p] )
           {
              DetSerial[(int)p] = Active_Wkfile.npd_bead_voltage[(int)p] ;
           }
        }
}

/* NPD state transition : waiting for detector to warmup */

static void NPDTransition1(POSITION p)
{
        INT32 tempActual;

        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        if (tempActual < NPD_MIN_OPERATING_TEMP)
        {
                /* turn off bead */
                SetDetInput1(p, OFF);

                /* temporarily disable H2 & air flow */
                DetGasOFF(p, NPD_FUEL_GAS);
                DetGasOFF(p, NPD_AIR_GAS);
        }
}

/* NPD state body: waiting for detector to warmup */

static void NPDBody1(POSITION p)
{
        INT32 tempActual;

        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* wait for detector temperature warmup if tempActual is < minimum */
        if (tempActual >= NPD_MIN_OPERATING_TEMP)
        {
                /* don't deglitch this transition:  we don't want the display to indicate "waiting"
                   if the detector is already warm, and a "false" start is harmless */
                GotoState(&sm[(int)p], NPD_ON);
        }
}

/* NPD state transition: bead ON */

static void NPDTransition2(POSITION p)
{
        /* enable H2 & air flow */
        DetGasON(p, NPD_FUEL_GAS);
        DetGasON(p, NPD_AIR_GAS);

        /* turn on bead */
        SetDetInput1(p, ON);
}

/* NPD state body: bead ON */

static void NPDBody2(POSITION p)
{
    ConvrgNPDVoltage(p);
        /* monitor detector temperature:  go to "waiting" state if tempActual is < (minimum - hysteresis) */
        if (NPDTooCold(p))
        {
                GotoState(&sm[(int)p], NPD_ON_WAIT_TEMP);
        }
}

/* NPD state transition: waiting for detector to warmup */

static void NPDTransition3(POSITION p)
{
        INT32 tempActual;

        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* wait for detector temperature warmup if tempActual is < minimum */
        if (tempActual < NPD_MIN_OPERATING_TEMP)
        {
                /* turn off bead */
                SetDetInput1(p, OFF);

                /* temporarily disable H2 & air flow */
                DetGasOFF(p, NPD_FUEL_GAS);
                DetGasOFF(p, NPD_AIR_GAS);
        }
}

/* NPD state body: waiting for detector to warmup */

static void NPDBody3(POSITION p)
{
        INT32 tempActual;

        tempActual = GetTempActual((p == FRONT) ? pFRONT_DET_TEMP : pBACK_DET_TEMP);

        /* wait for detector temperature warmup if tempActual is < minimum */
        if (tempActual >= NPD_MIN_OPERATING_TEMP)
        {
                /* don't deglitch this transition:  we don't want the display to indicate "waiting"
                   if the detector is already warm, and a "false" start is harmless */
                GotoState(&sm[(int)p], NPD_SEEK_WAIT_READY);
        }
}

/* NPD state transition: wait for temperature readiness */

static void NPDTransition4(POSITION p)
{
        /* enable H2 & air flow */
        DetGasON(p, NPD_FUEL_GAS);
        DetGasON(p, NPD_AIR_GAS);

        /* turn on bead */
        SetDetInput1(p, ON);
}

/* NPD state body: wait for temperature to readiness */

static void NPDBody4(POSITION p)
{
#define NPD_LOOP_A 0
        INT32 error;

    ConvrgNPDVoltage(p);
        /* monitor detector temperature:  go to "waiting" state if tempActual is < (minimum - hysteresis) */
        if (NPDTooCold(p))
        {
                GotoState(&sm[(int)p], NPD_SEEK_WAIT_TEMP);
        }

        /* test for detector and oven readiness */
        (void)FineErrorOK(p, NPD_LOOP_A, &error) ; /* just to define the error */
    if (
          ( error < 0 )
          ||
          (
            ( DetSerial[(int)p] == Active_Wkfile.npd_bead_voltage[(int)p] ) &&
            ( !TestForException(OVEN_THERMAL) && !TestForException((p == FRONT)? DETA_THERMAL : DETB_THERMAL) )
          )
       )
        {
                GotoState(&sm[(int)p], NPD_START_SEEK);
        }
}

/* NPD state transition: adjust(coarse), sleep(30), repeat */

static void NPDTransition6 (POSITION p)
{
        INT32 power;

        /* exit state if offset > 5 pA */
        if (DetOffset(p) > 50)
        {
                GotoState(&sm[(int)p], NPD_START_A);
        }
        else
        {
                /* do coarse power adjustment */
                power = GetNPDBeadDACVolts(p);
                power += NPD_MAX_1SEC_CHG ;
                if (power > NPD_BEAD_POWER_MAX)
                {
                   GotoState(&sm[(int)p], NPD_ADJUST_FAIL);
                }
                else
                {
                   (void) SetNPDBeadVoltage(p, power);
                }
        }
}

/* NPD state body : monitor detector temperature */

static void NPDAdjustBody(POSITION p)
{
        /* monitor detector temperature:  go to "waiting" state if tempActual is < (minimum - hysteresis) */
        if (NPDTooCold(p))
        {
           GotoState(&sm[(int)p], NPD_SEEK_WAIT_TEMP);
        }
        else
        {
           ConvrgNPDVoltage(p);
        }
}

/* NPD state transition: adjust(a), sleep(a), repeat */

static void NPDTransition7 (POSITION p)
{
#define NPD_LOOP_A 0
        INT32 error, power;

        /* exit state if offset error within bounds */
        if (FineErrorOK(p, NPD_LOOP_A, &error))
        {
                GotoState(&sm[(int)p], NPD_START_B);
        }
        /* do fine power adjustment */
        else
        {
                /* set sleep time to constant */
                sm[(int)p].timeDuration = NPDk(p, NPD_LOOP_A + 6);

                /* apply power adjustment, clamp if necessary */
                power = GetNPDBeadDACVolts(p);
                power += NPDk(p, NPD_LOOP_A + 3) * ((error < 0) ? -1 : 1);
                if (power < NPD_BEAD_POWER_MIN || power > NPD_BEAD_POWER_MAX)
                {
                   GotoState(&sm[(int)p], NPD_ADJUST_FAIL);
                }
                else
                {
                   (void) SetNPDBeadVoltage(p, power);
                }
        }
}

/* NPD state transition: adjust(b), sleep(b), repeat */

static void NPDTransition8 (POSITION p)
{
#define NPD_LOOP_B 1
        INT32 error, power;

        /* exit state if offset error within bounds */
        if (FineErrorOK(p, NPD_LOOP_B, &error))
        {
                GotoState(&sm[(int)p], NPD_START_C);
        }
        /* retreat if offset error has become excessive */
        else if (!FineErrorOK(p, NPD_LOOP_A, &error))
        {
                GotoState(&sm[(int)p], NPD_START_A);
        }
        /* do fine power adjustment */
        else
        {
                /* set sleep time to constant */
                sm[(int)p].timeDuration = NPDk(p, NPD_LOOP_B + 6);

                /* apply power adjustment, clamp if necessary */
                power = GetNPDBeadDACVolts(p);
                power += NPDk(p, NPD_LOOP_B + 3) * ((error < 0) ? -1 : 1);
                if (power < NPD_BEAD_POWER_MIN || power > NPD_BEAD_POWER_MAX)
                {
                   GotoState(&sm[(int)p], NPD_ADJUST_FAIL);
                }
                else
                {
                   (void) SetNPDBeadVoltage(p, power);
                }
        }
}

/* NPD state transition: adjust(c), sleep(c), repeat */

static void NPDTransition9 (POSITION p)
{
#define NPD_LOOP_C 2
        INT32 error, power;

        /* exit state if offset within bounds */
        if (FineErrorOK(p, NPD_LOOP_C, &error))
        {
                GotoState(&sm[(int)p], NPD_START_VALIDATE);
        }
        /* retreat if offset error has become excessive */
        else if (!FineErrorOK(p, NPD_LOOP_B, &error))
        {
                GotoState(&sm[(int)p], NPD_START_B);
        }
        /* do fine power adjustment */
        else
        {
                /* set sleep time to constant */
                sm[(int)p].timeDuration = NPDk(p, NPD_LOOP_C + 6);

                /* apply power adjustment, clamp if necessary */
                power = GetNPDBeadDACVolts(p);
                power += NPDk(p, NPD_LOOP_C + 3) * ((error < 0) ? -1 : 1);
                if (power < NPD_BEAD_POWER_MIN || power > NPD_BEAD_POWER_MAX)
                {
                   GotoState(&sm[(int)p], NPD_ADJUST_FAIL);
                }
                else
                {
                   (void) SetNPDBeadVoltage(p, power);
                }
        }
}

/* NPD state transition: validate bead setpt */
static void NPDTransition10 (POSITION p)
{
        /* set state sleep time to equib time specified by user */
        /* equib time is stored in deciminutes, but we need seconds here */
        sm[(int)p].timeDuration = (((INT32)Active_Wkfile.det_equib_time[(int)p] * 60) + 50) / 100;
}

/* NPD state body: validate bead setpt */

static void NPDBody10(POSITION p)
{
        INT32 error;

        /* monitor detector temperature:  go to "waiting" state if tempActual is < (minimum - hysteresis) */
        if (NPDTooCold(p))
        {
                GotoState(&sm[(int)p], NPD_SEEK_WAIT_TEMP);
        }

        if (!FineErrorOK(p, NPD_LOOP_C, &error))
        {
                GotoState(&sm[(int)p], NPD_START_C);
        }

        (void)error;  /* silence warning */
}

/* NPD state transition: shutdown after pneumatics or adjust failure */

static void NPDTransitionShutdown (POSITION p)
{
        /* pneumatics system turns all detector gases off after a shutdown, but
           we need to do it here in case this shutdown was from an adjust failure */
        DetGasOFF(p, NPD_FUEL_GAS);
        DetGasOFF(p, NPD_AIR_GAS);

        /* to protect bead from damage, turn off working setpoint for bead power */
        SetDetInput1(p, OFF);
}

/* Abort an NPD ignition/extinguish already in progress  */

static void NPDAbort(POSITION p)
{
        /* stop state machine */
        GotoState(&sm[(int)p], NPD_OFF);

        /* restore H2 and air setpoints */
        DetGasON(p, NPD_FUEL_GAS);
        DetGasON(p, NPD_AIR_GAS);
}

/* Initiate a flame ignition sequence */

void NPDIgnite(POSITION p, int seek)
{
        /* abort an ignition/extinguish that might already be in progress */
        NPDAbort(p);

        if (seek)
        {
                /*
                ** To protect a possibly new bead from damage, be sure the
                ** bead voltage is at a "safe" value before beginning an offset seek.
                */
                if (GetNPDBeadDACVolts(p) > 3500)
                {
                        (void) SetNPDBeadVoltage(p, NPD_BEAD_POWER_DEFAULT);
                }

                /* begin "seek" state machine */
                GotoState(&sm[(int)p], NPD_SEEK_WAIT_TEMP);
        }
        else
        {
                /* begin "on" state machine */
                GotoState(&sm[(int)p], NPD_ON_WAIT_TEMP);
        }

        /* set flame setpoint to "ON" */
        if (p == FRONT)
        {
                Active_Wkfile.det_flags.flag.deta_flame = (int) ON;
        }
        else
        {
                Active_Wkfile.det_flags.flag.detb_flame = (int) ON;
        }
}

/* Initiate an NPD extinguish sequence */

void NPDExtinguish(POSITION p)
{
        /* abort an ignition/extinguish that might already be in progress and move state machine to NPD_OFF state */
        NPDAbort(p);

        /* turn off bead */
        SetDetInput1(p, OFF);
        DetSerial[(int)p] = 0;
        /* set flame setpoint to "OFF" */
        if (p == FRONT)
        {
                Active_Wkfile.det_flags.flag.deta_flame = (int)OFF;
        }
        else
        {
                Active_Wkfile.det_flags.flag.detb_flame = (int)OFF;
        }
}

/* Initiate a NPD shutdown sequence */

void NPDShutdown(POSITION p)
{
        GotoState(&sm[(int)p], NPD_SHUTDOWN);
}


/* return ON_OFF status of NPD */

ON_OFF NPDStatus(POSITION p)
{
        /* is detector on? */
        if (sm[(int)p].state == NPD_ON)
        {
                return ON;
        }

        /* is detector off? */
        else if (sm[(int)p].state == NPD_OFF)
        {
                return OFF;
        }

        /* are we waiting for the detector to get warm? */
        else if ( (sm[(int)p].state == NPD_ON_WAIT_TEMP) || (sm[(int)p].state == NPD_SEEK_WAIT_TEMP) )
        {
                return WAITING;
        }

        /* are we waiting for other readiness? */
        else if (sm[(int)p].state == NPD_SEEK_WAIT_READY)
        {
                return WAITING2;
        }

        /* are we performing an offset seek? */
        else if ( (sm[(int)p].state >= NPD_START_SEEK) && (sm[(int)p].state <= NPD_START_VALIDATE) )
        {
                return TRANSITION;
        }

        /* is detector shutdown? */
        else if (sm[(int)p].state == NPD_SHUTDOWN)
        {
                return SHUTDOWN;
        }

        /* only state left is adjust failure */
        else
        {
                return FAIL;
        }
}

/**********************************************************************/

/* ECD state table */
static const StateTable ECDStateTable =
{
        /* {next, time, transition, body}               state - description */
#define ECD_ON 0
        {0,  0,         NullFunction,   NullFunction},  /* 0 - on (same as idle) */
#define ECD_SEEK_WAIT_READY 1
        {1, -1,         NullFunction,   ECDBody1},      /* 1 - wait for readiness */
#define ECD_START_SEEK 2
        {3,  3,         ECDTransition2, NullFunction},  /* 2 - setup for adjust */
        {3,  3,         ECDTransition3, NullFunction},  /* 3 - do adjust */
#define ECD_START_VALIDATE 4
        {5,  3,         ECDTransition4, NullFunction},  /* 4 - setup for validate */
#define ECD_DO_VALIDATE 5
        {0,  0,         ECDTransition5, NullFunction},  /* 5 - do validate */
#define ECD_ADJUST_FAIL 6
        {6, -1,         NullFunction,   NullFunction},  /* 6 - shutdown due to adjust failure */
};

/* ECD state body: wait for readiness */

static void ECDBody1(POSITION p)
{
        /* test for detector and oven readiness */
        if ( !TestForException(OVEN_THERMAL) && !TestForException((p == FRONT)? DETA_THERMAL : DETB_THERMAL) )
        {
                GotoState(&sm[(int)p], ECD_START_SEEK);
        }
}

/* ECD state transition: setup for adjust */

static void ECDTransition2(POSITION p)
{
        /* initialize 8-bit DAC increment to MSB = ON */
        sm[(int)p].itch = sm[(int)p].scratch = 0x80;    /* itch is DAC setpoint, scratch is DAC bit */
        (void)SetECDDAC(p, sm[(int)p].itch);
}

/* ECD state transition: do adjust */

static void ECDTransition3(POSITION p)
{
        INT32 dac;

        /* are we done? */
        if (sm[(int)p].scratch == 0)    /* out of DAC bits? */
        {
                /* remember error = (actual - target) */
                sm[(int)p].scratch = DetOffset(p) - (10 * GetECDTargetOffset(p));

                /* validate that this is the "best" setpoint */
                GotoState(&sm[(int)p], ECD_START_VALIDATE);
        }
        else
        {
                /* if we overshot the target then subtract the last DAC increment */
                /* TargetOffset is scaled in integral units, DetOffset is in 0.1 units */
                if (DetOffset(p) > (10 * GetECDTargetOffset(p)))
                {
                        sm[(int)p].itch -= sm[(int)p].scratch;
                }

                /* shift to the next DAC bit */
                sm[(int)p].scratch >>= 1;
                sm[(int)p].itch += sm[(int)p].scratch;

                /* clamp the result if necessary */
                dac = sm[(int)p].itch;
                if (dac < ECD_DAC_MIN)
                {
                        dac = ECD_DAC_MIN;
                }
                if (dac > ECD_DAC_MAX)
                {
                        dac = ECD_DAC_MAX;
                }

                /* apply the next DAC bit */
                (void)SetECDDAC(p, dac);
        }
}

/* ECD state transition: setup for validate */

static void ECDTransition4(POSITION p)
{
        INT32 dac_setpoint;

        /* We have found the highest DAC setpoint that produces an actual offset less than the target offset. */
        /* To complete the adjustment, we increment the DAC setpoint by 1 and see if that gives a smaller target error. */
        /* But first, be sure the adjustment is within the range of the DAC. */
        dac_setpoint = GetECDDAC(p);
        if (dac_setpoint > ECD_DAC_MIN && dac_setpoint < ECD_DAC_MAX)
        {
                (void)SetECDDAC(p, GetECDDAC(p) + 1);
        }
        else
        {
                /*
                ** We reached the max DAC setpoint without ever attaining the target offset.
                ** Check error to see if we by chance are within one user unit of the target.
                ** This is an arbitrarily chosen measure of success.
                */
                if (abs(sm[(int)p].scratch) > 1)
                {
                        /* shutdown */
                        GotoState(&sm[(int)p], ECD_ADJUST_FAIL);
                }
                else
                {
                        /* it's a miracle! */
                        GotoState(&sm[(int)p], ECD_ON);
                }
        }
}

/* ECD state transition: do validate */

static void ECDTransition5(POSITION p)
{
        /* did adding 1 to DAC setpoint make the target error worse? */
        if ( abs(DetOffset(p) - (10 * GetECDTargetOffset(p))) >= abs(sm[(int)p].scratch) )
        {
                /* decrement the DAC back to the old setpoint */
                (void)SetECDDAC(p, GetECDDAC(p) - 1);
        }
}

void ECDStartSeek(POSITION p)
{
        /* start state machine */
        GotoState(&sm[(int)p], ECD_SEEK_WAIT_READY);
}

void ECDStopSeek(POSITION p)
{
        /* stop state machine */
        GotoState(&sm[(int)p], ECD_ON);
}

ON_OFF ECDStatus(POSITION p)
{
        /* is detector on? */
        if (sm[(int)p].state == ECD_ON)
        {
                return ON;
        }

        /* are we waiting for other readiness? */
        else if (sm[(int)p].state == ECD_SEEK_WAIT_READY)
        {
                return WAITING2;
        }

        /* are we performing an offset seek? */
        else if ( (sm[(int)p].state >= ECD_START_SEEK) && (sm[(int)p].state <= ECD_DO_VALIDATE) )
        {
                return TRANSITION;
        }

        /* only state left is adjust failure */
        else
        {
                return FAIL;
        }
}

/**********************************************************************/

/* OEM state table */
static const StateTable OEMStateTable =
{
        /* {next, time, transition, body}               state - description */
        {1,  0,         NullFunction,   NullFunction},  /* 0 - leave at once     */
#define OEM_FOREVER 1
        {1, -1,         NullFunction,   OEMDetService}, /* 1 - here forever      */
};

/* OEM state body: manage automatic pulsed controls */

static void OEMDetService(POSITION p)
{
   ChkOEMPulses(p) ;
   OEMDetRdyChk(p) ;
}

static U_INT8 TCDRefGasOK( POSITION p)
{
/*  ../include/p_ui.h:478:ON_OFF GetDetGasState ( pDETECTOR_STATUS status, U_INT8 gas ); */

  return(
         ( GetDetGasState( pDetPtr(p) , TCD_REF_GAS ) == ON   )
         &&
         (
           ( 10*GetDetectorGasFlowActual( pDetPtr(p), TCD_REF_GAS ) >= 8*GetMinDetGasFlow( pDetPtr(p),TCD_REF_GAS ))
            ||
           !IsDetectorEpc(p)
         )
        );
}

static void OEMDetRdyChk(POSITION p)
{
   BIT8  idx ;

   if (OEMPollTym[(BIT8)p] != 0 )
   {
      OEMPollCount[(BIT8)p] ++ ;
      if (OEMPollCount[(BIT8)p] >= OEMPollTym[(BIT8)p] )
      {
         OEMPollCount[(BIT8)p] = 0 ;
         for (idx = 0; idx < NUM_OF_NTRYS/2; idx++)
         {
            if ( (1 << idx) & OEMPollValue[(BIT8)p] )
            {
              SetException( Oem_Rdy_Xcptn[idx + 9*(BIT8)p  ] );
            }
            else
            {
              ClearNewException( Oem_Rdy_Xcptn[idx + 9*(BIT8)p  ] );
            };
         }
         OEMPollState |=  ( 0x0c12 >> ( (BIT8)p + (BIT8)p ) ) + (BIT8)p  ;
      }
   };
}

static void ChkOEMPulses(POSITION p)
{
   BIT8   *pulse;
   BIT8   channl;
   U_INT16 mask;

#if 0
   for ( channl = 9; channl <= 10 ; channl++ )  /* keep diagnostic_1,2 filtered values ~ uptodate */
   {

      if (GetOEMCtlPresence(p,channl) ) (void)GetOEMDiagValu(p,channl-8) ;

   };
#endif

   for ( channl = 1; channl < 6; channl++ )
   {

             mask = 1 << (channl-1);

             INTERRUPTS_OFF

             switch ( channl )
             {
                default:
                case 1:
                        pulse = (p == FRONT)? &FDCtl1.pulse_left: &BDCtl1.pulse_left  ;
                        if ( *pulse == 1 ) SetDetInput1(p, OFF);
                        break ;
                case 2:
                        pulse = (p == FRONT)? &FDCtl2.pulse_left: &BDCtl2.pulse_left  ;
                        if ( *pulse == 1 ) SetDetInput2(p, OFF);
                        break ;
                case 3:
                        pulse = (p == FRONT)? &FDCtl3.pulse_left: &BDCtl3.pulse_left  ;
                        if ( *pulse == 1 ) SetDetInput3(p, OFF);
                        break ;
                case 4:
                        pulse = (p == FRONT)? &FDCtl4.pulse_left: &BDCtl4.pulse_left  ;
                        if ( *pulse == 1 ) SetDetInput4(p, OFF);
                        break ;
                case 5:
                        pulse = (p == FRONT)? &FDCtrl5.pulse_left: &BDCtrl5.pulse_left  ;
                        if ( *pulse == 1 ) SetDetInput5(p, OFF);
                        break ;
             }

             if ( *pulse == 1 ) Active_Wkfile.det_cpf_ctl[p].on_off ^= mask ; /* clear wkfile bit */

             INTERRUPTS_ON

             if ( *pulse > 0 ) (*pulse)-- ;
   };
}

/**********************************************************************/

/*
** InitDetectorSubtask - Initialize the detector subtask.
**      Called during startup by InitializeDetectors().
*/

void InitDetectorSubtask( INT32 i)
{
        /* initialize the front and back detector state machines */
        sm[i].state = 0;
        sm[i].p = (POSITION)i;
        sm[i].attempts = 0;
        sm[i].counter = 0;
        sm[i].errors = 0;
        /* sm[i].timeStart = don't care */
        /* sm[i].timeLast = don't care */
        /* sm[i].timeDuration = don't care */
        /* sm[i].scratch = don't care */
        /* sm[i].diag = don't care */
        /* sm[i].timeDuration = don't care */

        /* See if the detector has been configured as signal only, */
        /* otherwise use the task for the type of detector board.  */
        if ( GetElectrometerType((POSITION)i) == FALSE )
        {
           switch (GetDetectorType((POSITION)i))
           {
           case ECD:
                sm[i].table = (StateTable *)ECDStateTable;
                break;
           case FID:
           case FPD:
                sm[i].table = (StateTable *)FlameStateTable;
                break;
           case NPD:
                sm[i].table = (StateTable *)NPDStateTable;
                break;
           case TCD:
                sm[i].table = (StateTable *)TCDStateTable;
                break;
           case OEM_AIB:
           case OEM_FID:
                sm[i].table = (StateTable *)OEMStateTable;
            sm[i].state = 1;
                break;
           case uECD:
           case LDID:
           default:
                sm[i].table = (StateTable *)NullStateTable;
           }
        }
        else
        {
           /* Electrometer:  signal only mode */
           sm[i].table = (StateTable *)NullStateTable;
        }
}

void InitDetectorSubtasks(void)
{
   INT32 i;

   for (i = 0; i < MAX_DETECTORS; i++)
   {
      InitDetectorSubtask(i);
   }
}

/**********************************************************************/

/*  EvaluateLDIDPlasma - determine plasma state given current state */
/*  For reading values between the HI and LO thresholds, just keep  */
/*  the current state to provide hysterisis.                        */

ON_OFF EvaluateLDIDPlasma (ON_OFF plasma, U_INT16 rdg)
{
   if (rdg > LDID_PLASMA_SHUTDOWN_THRESHOLD) return SHUTDOWN;

   if (rdg < LDID_PLASMA_ON_LO_THRESHOLD) return OFF;

   if (rdg >= LDID_PLASMA_ON_HI_THRESHOLD) return ON;
   else                                    return plasma;

}


/*
** EvalDetectorStateReadiness - evaluate the detector readiness
**      exceptions that are functions of state machine state
*/

void EvalDetectorStateReadiness(POSITION p, U_INT16 tym )
{
    BIT8  idx ;
    DET_TYPE det;

        det = GetDetectorType(p);

        switch (det)
        {
        case OEM_AIB:
        case OEM_FID:
           if (OEMPollTym[(BIT8)p] != 0 ) break ;
           else
           {
              if (tym > 99 ) break ; /* fall thru @ 1hz , not 5Hz ; saves cpu cycles */
              for (idx = 0; idx < NUM_OF_NTRYS/2; idx++)
              {
                 SetEvalReadyBit( Oem_Rdy_Xcptn[idx + 9*(BIT8)p  ] );
              }
           }
           break;

        default:
           if (tym > 99 ) break ; /* fall thru @ 1hz , not 5Hz ; saves cpu cycles */
           for (idx = 0; idx < NUM_OF_NTRYS/2; idx++)
           {
              SetEvalReadyBit( Oem_Rdy_Xcptn[idx + 9*(BIT8)p  ] );
           }
        }

        switch (det)
        {
        case FID:
        case FPD:
                /* XXXX_DET_LOW_TEMP */
                if ( sm[(int)p].state == FLAME_WAIT )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP);
                }

                /* XXXX_DET_IGNITING */
                if ( (sm[(int)p].state >= FLAME_START_IGNITE) && (sm[(int)p].state <= FLAME_END_IGNITE) )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING);
                }

                /* XXXX_DET_SHUTDOWN */
                if ( sm[(int)p].state == FLAME_SHUTDOWN )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN , 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN );
                }

                /* XXXX_DET_ADJUSTING */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING);

                /* XXXX_DET_EQUIB */
                ClearNewException((p == FRONT) ? FRNT_DET_EQUIB : BACK_DET_EQUIB );

                /* XXXX_DET_ADJUST_FAIL */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL);

                /* XXXX_DET_SLEWING  */
                ClearNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING );

                break;

        case NPD:
                /* XXXX_DET_LOW_TEMP */
                if ( (sm[(int)p].state == NPD_ON_WAIT_TEMP) || (sm[(int)p].state == NPD_SEEK_WAIT_TEMP) )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP);
                }

                /* XXXX_DET_ADJUSTING */
                if ( (sm[(int)p].state >= NPD_SEEK_WAIT_READY) && (sm[(int)p].state <= NPD_START_VALIDATE) )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING);
                }

                /* XXXX_DET_SLEWING  */
                if (
                     ( (sm[(int)p].state == NPD_ON) || (sm[(int)p].state == NPD_SEEK_WAIT_READY) ) &&
             ( Active_Wkfile.npd_bead_voltage[(int)p] != DetSerial[(int)p] )
           )
                {
                        SetNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING , 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING );
                }

                /* XXXX_DET_SHUTDOWN */
                if ( sm[(int)p].state == NPD_SHUTDOWN )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN , 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN );
                }

                /* XXXX_DET_IGNITING */
                ClearNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING);

                /* XXXX_DET_EQUIB */
                ClearNewException((p == FRONT) ? FRNT_DET_EQUIB : BACK_DET_EQUIB );

                /* XXXX_DET_ADJUST_FAIL */
                if ( sm[(int)p].state == NPD_ADJUST_FAIL )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL);
                }

                break;

        case TCD:
                /* XXXX_DET_LOW_TEMP */
                if ( sm[(int)p].state == TCD_WAIT )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP);
                }

                /* XXXX_DET_SHUTDOWN */
                if ( sm[(int)p].state == TCD_SHUTDOWN )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN , 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN );
                }

                /* XXXX_DET_ADJUSTING */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING);

                /* XXXX_DET_IGNITING */
                ClearNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING);

                /* XXXX_DET_EQUIB */
                ClearNewException((p == FRONT) ? FRNT_DET_EQUIB : BACK_DET_EQUIB );

                /* XXXX_DET_ADJUST_FAIL */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL);

                /* XXXX_DET_SLEWING  */
                ClearNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING );

                break;

        case ECD:
                /* XXXX_DET_LOW_TEMP */
                ClearNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP);

                /* XXXX_DET_IGNITING */
                ClearNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING);

                /* XXXX_DET_ADJUSTING */
                if ( (sm[(int)p].state >= ECD_SEEK_WAIT_READY) && (sm[(int)p].state <= ECD_DO_VALIDATE) )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING);
                }

                /* XXXX_DET_SHUTDOWN */
                ClearNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN);

                /* XXXX_DET_EQUIB */
                ClearNewException((p == FRONT) ? FRNT_DET_EQUIB : BACK_DET_EQUIB );

                /* XXXX_DET_SLEWING  */
                ClearNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING );

                /* XXXX_DET_ADJUST_FAIL */
                if ( sm[(int)p].state == ECD_ADJUST_FAIL )
                {
                        SetNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL, 0, 0);
                }
                else
                {
                        ClearNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL);
                }

                break;

        default:
                /* XXXX_DET_LOW_TEMP */
                ClearNewException((p == FRONT) ? FRNT_DET_LOW_TEMP : BACK_DET_LOW_TEMP);

                /* XXXX_DET_IGNITING */
                ClearNewException((p == FRONT) ? FRNT_DET_IGNITING : BACK_DET_IGNITING);

                /* XXXX_DET_ADJUSTING */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUSTING : BACK_DET_ADJUSTING);

                /* XXXX_DET_SHUTDOWN */
                ClearNewException((p == FRONT) ? FRNT_DET_SHUTDOWN : BACK_DET_SHUTDOWN);

                /* XXXX_DET_EQUIB */
                ClearNewException((p == FRONT) ? FRNT_DET_EQUIB : BACK_DET_EQUIB );

                /* XXXX_DET_ADJUST_FAIL */
                ClearNewException((p == FRONT) ? FRNT_DET_ADJUST_FAIL : BACK_DET_ADJUST_FAIL);

                /* XXXX_DET_SLEWING  */
                ClearNewException((p == FRONT) ? FRNT_NPD_SLEWING  : BACK_NPD_SLEWING );
        }

        if (det == LDID)
        {
           if (p == FRONT) F_LDID_Plasma = EvaluateLDIDPlasma (F_LDID_Plasma, Det_Diaga_Rdg);
           else            B_LDID_Plasma = EvaluateLDIDPlasma (B_LDID_Plasma, Det_Diagc_Rdg);
        }
}
